﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;
using Supermodel.DomainModel;
using Supermodel.Exceptions;
using Supermodel.MVC.FormModels;
using Supermodel.MVC.FormModels.Metadata;
using Supermodel.Reflection;
using Supermodel.Section508;

namespace Supermodel.MVC.Extensions
{
    public static class HtmlHelperExtensions
    {
        #region RESTful ActionLink helpers
        public static MvcHtmlString RESTfulActionLink(this HtmlHelper html, HttpVerbs actionVerb, string linkText, object htmlAttributes, string actionName, object routeValues, string controllerName, string confMsg)
        {
            var result = new StringBuilder();
            result.AppendLine(html.SupermodelBeginForm(actionName, controllerName, routeValues).ToString());
            result.AppendLine(ActionLinkFormContent(html, linkText, actionVerb, htmlAttributes, confMsg).ToString());
            result.AppendLine(html.SupermodelEndForm().ToString());
            return MvcHtmlString.Create(result.ToString());
        }
        public static MvcHtmlString RESTfulActionLink(this HtmlHelper html, HttpVerbs actionVerb, string linkText, object htmlAttributes, string actionName, object routeValues, string confMsg)
        {
            var result = new StringBuilder();
            result.AppendLine(html.SupermodelBeginForm(actionName, null /*controllerName*/, routeValues).ToString());
            result.AppendLine(ActionLinkFormContent(html, linkText, actionVerb, htmlAttributes, confMsg).ToString());
            result.AppendLine(html.SupermodelEndForm().ToString());
            return MvcHtmlString.Create(result.ToString());
        }
        private static MvcHtmlString ActionLinkFormContent(HtmlHelper htmlHelper, string linkText, HttpVerbs actionVerb, object htmlAttributes, string confMsg)
        {
            var result = new StringBuilder();

            //HttpMethodOverride
            switch (actionVerb)
            {
                case HttpVerbs.Delete:
                case HttpVerbs.Head:
                case HttpVerbs.Put:
                    result.AppendLine(htmlHelper.HttpMethodOverride(actionVerb).ToHtmlString());
                    break;
                case HttpVerbs.Post:
                    break;
                case HttpVerbs.Get:
                    throw new SupermodelException("HttpVerbs.Get is not supported in ActionLinkFormContent");
            }

            //submit
            if (!Section508Settings.Generate508ComplientHtml)
            {
                var aTag = new TagBuilder("a");
                aTag.MergeAttribute("href", "javascript:void(0);");

                if (confMsg != null) aTag.MergeAttribute("onclick", "if (confirm('" + confMsg + "')) $(this).parent().submit();");
                else aTag.MergeAttribute("onclick", "$(this).parent().submit();");

                aTag.MergeAttributes(ObjectToCaseSensitiveDictionary(htmlAttributes));
                aTag.InnerHtml = linkText;
                result.AppendLine(aTag.ToString());
            }
            else
            {
                var aTag = new TagBuilder("input");
                aTag.MergeAttribute("link", "");
                aTag.MergeAttribute("type", "submit");
                aTag.MergeAttribute("Value", linkText);
                if (confMsg != null) aTag.MergeAttribute("onclick", "return confirm('" + confMsg + "');");
                result.AppendLine(aTag.ToString());
            }

            return MvcHtmlString.Create(result.ToString());
        }
        private static Dictionary<string, object> ObjectToCaseSensitiveDictionary(object values)
        {
            var dict = new Dictionary<string, object>(StringComparer.Ordinal);
            if (values != null)
            {
                foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(values))
                {
                    object val = prop.GetValue(values);
                    dict[prop.Name] = val;
                }
            }
            return dict;
        }
        #endregion

        #region Private MvcHtmlString Form Methods
        public static MvcHtmlString SupermodelBeginForm(this HtmlHelper html)
        {
            string rawUrl = html.ViewContext.HttpContext.Request.RawUrl;
            return html.SupermodelFormHelper(rawUrl, FormMethod.Post, new RouteValueDictionary());
        }
        public static MvcHtmlString SupermodelBeginForm(this HtmlHelper html, string actionName, string controllerName, RouteValueDictionary routeValues)
        {
            return html.SupermodelBeginForm(actionName, controllerName, routeValues, FormMethod.Post, new RouteValueDictionary());
        }
        public static MvcHtmlString SupermodelBeginForm(this HtmlHelper html, string actionName, string controllerName, object routeValues)
        {
            return html.SupermodelBeginForm(actionName, controllerName, new RouteValueDictionary(routeValues), FormMethod.Post, new RouteValueDictionary());
        }
        public static MvcHtmlString SupermodelBeginForm(this HtmlHelper html, string actionName, string controllerName, RouteValueDictionary routeValues, FormMethod method, IDictionary<string, object> htmlAttributes)
        {
            string formAction = UrlHelper.GenerateUrl(null, actionName, controllerName, routeValues, html.RouteCollection, html.ViewContext.RequestContext, true);
            return html.SupermodelFormHelper(formAction, method, htmlAttributes);
        }
        // ReSharper disable UnusedParameter.Local
        private static MvcHtmlString SupermodelFormHelper(this HtmlHelper htmlHelper, string formAction, FormMethod method, IDictionary<string, object> htmlAttributes)
        // ReSharper restore UnusedParameter.Local
        {
            var builder = new TagBuilder("form");
            builder.MergeAttributes<string, object>(htmlAttributes);
            builder.MergeAttribute("action", formAction);
            builder.MergeAttribute("method", HtmlHelper.GetFormMethodString(method), true);
            builder.MergeAttribute("enctype", "multipart/form-data"); //This diffres for standard implemetation but we always want to have an ability to upload files
            //bool flag = html.ViewContext.ClientValidationEnabled && !html.ViewContext.UnobtrusiveJavaScriptEnabled;
            //if (flag) builder.GenerateId(html.ViewContext.FormIdGenerator()););
            return MvcHtmlString.Create(builder.ToString(TagRenderMode.StartTag));
        }
        public static MvcHtmlString SupermodelEndForm(this HtmlHelper html)
        {
            return MvcHtmlString.Create("</form>");
        }
        #endregion

        #region CRUD Helpers
        public static MvcHtmlString CRUDEdit<EntityT, FormModelT>(this HtmlHelper html, string pageTitle, bool readOnly = false)
            where EntityT : Entity<EntityT>, new()
            where FormModelT : FormModelForEntityBase<EntityT>, new()
        {
            var result = new StringBuilder();
            var model = (FormModelT)html.ViewData.Model;

            result.AppendLine(html.SupermodelBeginForm().ToString());
            result.AppendLine("<h1>" + html.Encode(pageTitle));
            if (readOnly) result.AppendLine("(Read Only)");
            result.AppendLine("</h1>");

            if (model.Id == 0) result.AppendLine(html.HttpMethodOverride(HttpVerbs.Put).ToString());
            result.AppendLine(html.FormModelEditorForModel().ToString());

            if (!readOnly) result.AppendLine("<input type='submit' value='Save' /> ");

            long? parentId = null;
            if (ReflectionHelpers.IsClassADrivedFromClassB(model.GetType(), typeof(FormModelForChildEntityBase<,>))) parentId = (long?)model.PropertyGet("ParentId");
            result.AppendLine(html.ActionLink("Back", "List", new { parentId }).ToString());
            
            result.AppendLine(html.SupermodelEndForm().ToString());

            return MvcHtmlString.Create(readOnly ? result.ToString().DisableAllControls() : result.ToString());
        }
        
        public static MvcHtmlString CRUDList<EntityT>(this HtmlHelper html, ICollection<EntityT> items, string pageTitle, string controllerName)
        where EntityT : Entity<EntityT>, new()
        {
            return html.CRUDListHelper(items, pageTitle, controllerName, null);
        }
        public static MvcHtmlString CRUDChildrenList<EntityT>(this HtmlHelper html, ICollection<EntityT> items, string pageTitle, string controllerName, long parentId)
        where EntityT : Entity<EntityT>, new()
        {
            return html.CRUDListHelper(items, pageTitle, controllerName, parentId);
        }
        private static MvcHtmlString CRUDListHelper<EntityT>(this HtmlHelper html, ICollection<EntityT> items, string pageTitle, string controllerName, long? parentId) 
        where EntityT : Entity<EntityT>, new()
        {
            var result = new StringBuilder();
            if (parentId == null || parentId > 0)
            {
                result.AppendLine("<h1>" + html.Encode(pageTitle) + "</h1>");
                result.AppendLine("<div>");
                result.AppendLine("<table zebra='true'>");
                result.AppendLine("<tr>");
                result.AppendLine("<th> Name </th>");
                result.AppendLine("<th colspan=2> Actions </th>");
                result.AppendLine("</tr>");
                foreach (var item in items)
                {
                    result.AppendLine("<tr>");
                    result.AppendLine("<td>" + html.Encode(item.Label) + "</td>");
                    result.AppendLine("<td>" + html.ActionLink("Edit", "Edit", controllerName, new {id = item.Id, parentId}, null) + "</td>");
                    result.AppendLine("<td>");

                    result.AppendLine(html.RESTfulActionLink(HttpVerbs.Delete, "Delete", null, "Edit", new { id = item.Id, parentId }, controllerName, "Are you sure?").ToString());

                    result.AppendLine("</td>");
                    result.AppendLine("</tr>");
                }
                result.AppendLine("</table>");
                result.AppendLine("<p>" + html.ActionLink("Add New", "Edit", controllerName, new {id = 0, ParentId = parentId}, null) + "</p>");
                result.AppendLine("</div>");
            }
            return MvcHtmlString.Create(result.ToString());
        }
        #endregion
        
        #region Display Helpers
        //This one handles both display for Radio and Dropdown
        public static MvcHtmlString FormModelDropDownDisplay(this HtmlHelper html)
        {
            if (html.ViewData.Model == null) throw new NullReferenceException("Html.FormModelDropDownDisplay() is called for a model that is null");
            if (!(html.ViewData.Model is DropdownFormModel)) throw new InvalidCastException("Html.FormModelDropDownDisplay() is called for a model of type diffrent from DropdownFormModel.");

            var dropdown = (DropdownFormModel)html.ViewData.Model;
            return MvcHtmlString.Create(dropdown.SelectedLabel);
        }
        public static MvcHtmlString FormModelBinaryFileDisplay(this HtmlHelper html)
        {
            if (html.ViewData.Model.GetType() != typeof(BinaryFileFormModel)) throw new InvalidCastException("Html.FormModelBinaryFileDisplay() is called for a model of type diffrent from FormModelFormModelFile.");
            return MvcHtmlString.Create((((BinaryFileFormModel)html.ViewData.Model)).Name);
        } 
        #endregion

        #region Editor Helpers
        public static MvcHtmlString FormModelEditor(this HtmlHelper html)
        {
            if (html.ViewData.Model == null) throw new NullReferenceException("Html.FormModelEditor() is called for a model that is null");
            if (!(html.ViewData.Model is FormModelBase)) throw new InvalidCastException("Html.FormModelEditor() is called for a model of type diffrent from FormModelBase.");
            
            var result = new StringBuilder();
            if (html.ViewData.TemplateInfo.TemplateDepth <= 1)
            {
                result.AppendLine("<table style='max-width: 100%'>");
                var properties = html.ViewData.ModelMetadata.Properties
                    .Where(pm => pm.ShowForEdit && !html.ViewData.TemplateInfo.Visited(pm))
                    .OrderBy(pm => pm.AdditionalValues.ContainsKey("ScreenOrder") ? ((ScreenOrderAttribute)pm.AdditionalValues["ScreenOrder"]).Order : 100);
                 
                foreach (var prop in properties)
                {
                    //By default we do not scaffold ICollections
                    if (prop.ModelType.Name == typeof(ICollection<>).Name) continue;

                    if (prop.HideSurroundingHtml)
                    {
                        result.AppendLine(html.Editor(prop.PropertyName).ToString());
                    }
                    else
                    {
                        result.AppendLine("<tr>");

                        if (prop.ModelType != typeof(TextFormModel))
                        {
                            //Label
                            result.AppendLine("<td>");
                            result.AppendLine("<span>");

                            result.AppendLine(html.Label(prop.PropertyName).ToString());

                            if (!prop.AdditionalValues.ContainsKey("NoRequiredLabel"))
                            {
                                result.AppendLine(prop.IsRequired || prop.AdditionalValues.ContainsKey("ForceRequiredLabel") ? "<span class=\"redLabel\"><sup>*</sup></span>" : "");
                            }
                            result.AppendLine("</span>");
                            result.AppendLine("</td>");

                            //Value
                            result.AppendLine("<td>");
                            if (!prop.AdditionalValues.ContainsKey("DisplayOnly"))
                            {
                                result.AppendLine(html.FormModelEditor(prop.PropertyName).ToString());
                                result.AppendLine("<br />");
                                var validationMessage = html.ValidationMessage(prop.PropertyName);
                                if (validationMessage != null) result.AppendLine(validationMessage.ToString());
                            }
                            else
                            {
                                result.AppendLine(html.FormModelDisplay(prop.PropertyName).ToString());
                            }
                            result.AppendLine("</td>");
                        }
                        else
                        {
                            //Display-only Value
                            result.AppendLine("<td colspan='2'>");
                            result.AppendLine(html.FormModelDisplay(prop.PropertyName).ToString());
                            result.AppendLine("</td>");
                        }

                        result.AppendLine("</tr>");
                    }

                }
                result.AppendLine("</table>");
            }
            return MvcHtmlString.Create(result.ToString());
        }
        public static MvcHtmlString CheckBoxesList(this HtmlHelper html, string name, IEnumerable<SelectListItem> selectList, string cssClass)
        {
            var fullName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(name);
            if (String.IsNullOrEmpty(fullName)) throw new ArgumentException(@"Value cannot be null or empty.", "name");

            var result = new StringBuilder();
            if (cssClass == null) result.AppendLine("<ul>");
            else result.AppendLine("<ul class='" + cssClass + "'>");
            foreach (var item in selectList)
            {
                result.AppendLine("<li>");

                if (item.Selected) result.AppendLine(string.Format("<input name='{0}' type='checkbox' value='{1}' checked />", fullName, item.Value));
                else result.AppendLine(string.Format("<input name='{0}' type='checkbox' value='{1}' />", fullName, item.Value));
                
                result.AppendLine("<label>" + item.Text + "</label>");
                result.AppendLine("</li>");
            }
            result.AppendLine("</ul>");
            result.AppendLine(string.Format("<input name='{0}' type='hidden' value='' />", fullName));
            return MvcHtmlString.Create(result.ToString());

        }
        public static MvcHtmlString ListBoxFormModelEditor(this HtmlHelper html)
        {
            if (html.ViewData.Model == null) throw new NullReferenceException("Html.FormModelMultiSelectEditor() is called for a model that is null");
            if (!(html.ViewData.Model is ListBoxFormModel)) throw new InvalidCastException("Html.ListBoxFormModelEditor() is called for a model of type diffrent from ListBoxFormModel.");
            
            var multiSelect = (ListBoxFormModel)html.ViewData.Model;
            var fullName = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName("");
            var result = html.ListBox("", multiSelect.GetSelectListItemList()).ToString() + string.Format("<input name='{0}' type='hidden' value='' />", fullName);
            return MvcHtmlString.Create(result);
        }
        public static MvcHtmlString CheckboxesListFormModelEditor(this HtmlHelper html, string cssClass)
        {
            if (html.ViewData.Model == null) throw new NullReferenceException("Html.FormModelMultiSelectEditor() is called for a model that is null");
            if (!(html.ViewData.Model is CheckboxesListFormModel)) throw new InvalidCastException("Html.CheckboxesListFormModelEditor() is called for a model of type diffrent from CheckboxesListFormModel.");

            var multiSelect = (CheckboxesListFormModel)html.ViewData.Model;
            return html.CheckBoxesList("", multiSelect.GetSelectListItemList(), cssClass);
        }
        
        public static MvcHtmlString DropDownFormModelEditor(this HtmlHelper html)
        {
            if (html.ViewData.Model == null) throw new NullReferenceException("Html.DropDownFormModelEditor() is called for a model that is null");
            if (!(html.ViewData.Model is DropdownFormModel)) throw new InvalidCastException("Html.DropDownFormModelEditor() is called for a model of type diffrent from DropdownFormModel.");

            var dropdown = (DropdownFormModel)html.ViewData.Model;
            return html.DropDownList(/*"SelectedValue"*/ "", dropdown.GetSelectListItemList());
        }
        public static MvcHtmlString RadioSelectFormModelEditor(this HtmlHelper html)
        {
            if (html.ViewData.Model == null) throw new NullReferenceException("Html.RadioSelectFormModelEditor() is called for a model that is null");
            if (!(html.ViewData.Model is RadioSelectFormModel)) throw new InvalidCastException("Html.RadioSelectFormModelEditor() is called for a model of type diffrent from RadioSelectFormModel.");

            var radio = (RadioSelectFormModel)html.ViewData.Model;
            var result = new StringBuilder();
            result.AppendLine("<div>");
            foreach (var option in radio.Options)
            {
                result.AppendLine(html.RadioButton(/*"SelectedValue"*/ "", option.Value, (radio.SelectedValue == option.Value)) + option.Label);
                if (radio.Layout == RadioSelectFormModel.RadioLayout.Vertical) result.AppendLine("<br />");
            }
            result.AppendLine("</div>");
            return MvcHtmlString.Create(result.ToString());
        }
        public static MvcHtmlString BinaryFileFormModelEditor(this HtmlHelper html)
        {
            if (html.ViewData.Model.GetType() != typeof(BinaryFileFormModel)) throw new InvalidCastException("Html.BinaryFileFormModelEditor() is called for a model of type diffrent from BinaryFileFormModel.");
            var model = ((BinaryFileFormModel)html.ViewData.Model);
            string fileInputId = html.ViewData.ModelMetadata.PropertyName;
            string fileInputName = html.ViewData.ModelMetadata.PropertyName; 

            var result = new StringBuilder();
            
            if (string.IsNullOrEmpty(model.Name)) 
            {
                result.AppendLine(string.Format("Pick a File: <input type=\"file\" name=\"{0}\" id=\"{1}\" />", fileInputName, fileInputId));
            }
            else
            {
                result.AppendLine(string.Format("Replace File with: <input type=\"file\" name=\"{0}\" id=\"{1}\" />", fileInputName, fileInputId));
                var route = new { id = html.ViewContext.RouteData.Values["id"], parentId = HttpContext.Current.Request.QueryString["parentId"], pn = html.ViewData.ModelMetadata.PropertyName };
                result.AppendLine("<br />" + html.ActionLink(model.Name, "GetBinaryFile", route));
                if (!html.ViewData.ModelMetadata.IsRequired)
                {
                    result.AppendLine("<br />" + html.ActionLink("Delete File", "DeleteBinaryFile", route, new { onclick = "return confirm('This will permanently delete the file. Are you sure?');" }));
                }
            }
            return MvcHtmlString.Create(result.ToString());
        } 
        #endregion

        #region FormModel Display & Editor Helpers
        public static MvcHtmlString FormModelEditor(this HtmlHelper html, string expression)
        {
            var modelMetadata = ModelMetadata.FromStringExpression(expression, html.ViewData);
            var htmlFieldName = ExpressionHelper.GetExpressionText(expression);

            if (modelMetadata.Model as IFormModelEditorTemplate != null && (modelMetadata.Model as IFormModelEditorTemplate).GetIEditorTemplateImplemented())
            {
                return (modelMetadata.Model as IFormModelEditorTemplate).EditorTemplate(html.MakeNestedHtmlHelper(modelMetadata, htmlFieldName));
            }
            return html.Editor(expression);
        }
        public static MvcHtmlString FormModelEditorFor<ModelT, ValueT>(this HtmlHelper<ModelT> html, Expression<Func<ModelT, ValueT>> expression)
        {
            var modelMetadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
            var htmlFieldName = ExpressionHelper.GetExpressionText(expression);

            if (modelMetadata.Model as IFormModelEditorTemplate != null && (modelMetadata.Model as IFormModelEditorTemplate).GetIEditorTemplateImplemented())
            {
                return (modelMetadata.Model as IFormModelEditorTemplate).EditorTemplate(html.MakeNestedHtmlHelper(modelMetadata, htmlFieldName));
            }
            return html.EditorFor(expression);
        }
        public static MvcHtmlString FormModelEditorForModel(this HtmlHelper html)
        {
            var modelMetadata = html.ViewData.ModelMetadata;

            if (modelMetadata.Model as IFormModelEditorTemplate != null && (modelMetadata.Model as IFormModelEditorTemplate).GetIEditorTemplateImplemented())
            {
                return (modelMetadata.Model as IFormModelEditorTemplate).EditorTemplate(html);
            }
            return html.FormModelEditorForModel();
        }

        public static MvcHtmlString FormModelDisplay(this HtmlHelper html, string expression)
        {
            var modelMetadata = ModelMetadata.FromStringExpression(expression, html.ViewData);
            var htmlFieldName = ExpressionHelper.GetExpressionText(expression);

            if (modelMetadata.Model as IFormModelDisplayTemplate != null && (modelMetadata.Model as IFormModelDisplayTemplate).GetIDisplayTemplateImplemented())
            {
                return (modelMetadata.Model as IFormModelDisplayTemplate).DisplayTemplate(html.MakeNestedHtmlHelper(modelMetadata, htmlFieldName));
            }
            return html.Display(expression);
        }
        public static MvcHtmlString FormModelDisplayFor<ModelT, ValueT>(this HtmlHelper<ModelT> html, Expression<Func<ModelT, ValueT>> expression)
        {
            var modelMetadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
            var htmlFieldName = ExpressionHelper.GetExpressionText(expression);

            if (modelMetadata.Model as IFormModelDisplayTemplate != null && (modelMetadata.Model as IFormModelDisplayTemplate).GetIDisplayTemplateImplemented())
            {
                return (modelMetadata.Model as IFormModelDisplayTemplate).DisplayTemplate(html.MakeNestedHtmlHelper(modelMetadata, htmlFieldName));
            }
            return html.DisplayFor(expression);
        }
        public static MvcHtmlString FormModelDisplayForModel(this HtmlHelper html)
        {
            var modelMetadata = html.ViewData.ModelMetadata;

            if (modelMetadata.Model as IFormModelDisplayTemplate != null && (modelMetadata.Model as IFormModelDisplayTemplate).GetIDisplayTemplateImplemented())
            {
                return (modelMetadata.Model as IFormModelDisplayTemplate).DisplayTemplate(html);
            }
            return html.DisplayForModel();
        }
        
        private static HtmlHelper MakeNestedHtmlHelper(this HtmlHelper html, ModelMetadata metadata, string htmlFieldName)
        {
            var viewData = new ViewDataDictionary(html.ViewDataContainer.ViewData) 
            {
                Model = metadata.Model,
                ModelMetadata = metadata,
                TemplateInfo = new TemplateInfo 
                {
                    FormattedModelValue = metadata.Model,
                    HtmlFieldPrefix = html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldName(htmlFieldName),
                }
            };
            return new HtmlHelper(new ViewContext(html.ViewContext, html.ViewContext.View, viewData, html.ViewContext.TempData, html.ViewContext.Writer), new ViewDataContainer(viewData));
        }
        #endregion

        #region String and MVCHtmlString Extensions
        public static string DisableAllControls(this string str)
        {
            return str.Replace("<input ", "<input disabled ").Replace("<textarea ", "<textarea disabled ").Replace("<select ", "<select disabled ");
        }
        public static MvcHtmlString DisableAllControls(this MvcHtmlString mvcStr)
        {
            return MvcHtmlString.Create(mvcStr.ToString().DisableAllControls());
        }        
        #endregion

        #region Private Helpers
        private static IEnumerable<SelectListItem> GetSelectData(this HtmlHelper htmlHelper, string name)
        {
            object o = null;
            if (htmlHelper.ViewData != null)
            {
                o = htmlHelper.ViewData.Eval(name);
            }
            if (o == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        @"An error occurred when trying to create the IModelBinder '{0}'. Make sure that the binder has a public parameterless constructor.",
                        name));
            }
            var selectList = o as IEnumerable<SelectListItem>;
            if (selectList == null)
            {
                throw new InvalidOperationException(
                    String.Format(
                        CultureInfo.CurrentCulture,
                        @"The model item passed into the dictionary is of type '{0}', but this dictionary requires a model item of type '{1}'.",
                        name,
                        o.GetType().FullName));
            }
            return selectList;
        }
        #endregion
    }

    public class ViewDataContainer : IViewDataContainer
    {
        public ViewDataContainer(ViewDataDictionary viewData)
        {
            ViewData = viewData;
        }

        public ViewDataDictionary ViewData { get; set; }
    }  
}
